/**
 * Copyright (c) 2012-2014 All Rights Reserved.
 */
package com.easy.framwork.utils.bean;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import com.easy.framwork.exceptions.ConvertException;
import com.easy.framwork.utils.StringUtils;

/**
 * 
 * @author wy
 * @version v 0.1 2014-1-2 下午9:57:26 wy Exp $
 */
public class PropertyDescriptorsCache {

    /**
     * Map keyed by class containing IntrospectionCache. Needs to be a
     * WeakHashMap with WeakReferences as values to allow for proper garbage
     * collection in case of multiple class loaders.
     */
    public static final Map<Class<?>, Object> classCache = Collections
        .synchronizedMap(new WeakHashMap<Class<?>, Object>());

    /** 字段信息,主要用于获取注解信息 key为字段名 */
    private final Map<String, Field> fieldCache = new HashMap<String, Field>();

    /** 类的属性信息，key为属性名 */
    private final Map<String, PropertyDescriptor> propertyDescriptorCache;

    /**
     * Instantiates a new Introspection cache.
     * 
     * @param beanClass the bean class
     * @throws IntrospectionException
     */
    private PropertyDescriptorsCache(Class<?> beanClass) {

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
            // 从Introspector缓存立即移除类，在类加载终止时允许适当的垃圾收集
            // 我们不管如何总是缓存在这里,这是一个GC友好的方式，对比于IntrospectionCache，
            // Introspector没有使用弱引用作为WeakHashMap的值
            Class<?> classToFlush = beanClass;
            do {
                Introspector.flushFromCaches(classToFlush);
                classToFlush = classToFlush.getSuperclass();
            }
            while (classToFlush != null);

            this.propertyDescriptorCache = new HashMap<String, PropertyDescriptor>();

            // 此调用较慢，所以我们只执行一次
            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

            for (PropertyDescriptor pd : pds) {

                if (Class.class.equals(beanClass) && "classLoader".equals(pd.getName())) {
                    // 忽略 Class.getClassLoader() 方法 - 没有人会需要绑定到那
                    continue;
                }

                this.propertyDescriptorCache.put(pd.getName(), pd);
            }
            Class<?> clazz = beanClass;

            while (clazz != Object.class) {

                Field[] fields = clazz.getDeclaredFields();

                for (Field f : fields) {
                    if (Modifier.isFinal(f.getModifiers()) || fieldCache.containsKey(f.getName())) {
                        continue;
                    }

                    fieldCache.put(f.getName(), f);
                }

                clazz = clazz.getSuperclass();
            }
        }
        catch (IntrospectionException e) {
            throw new ConvertException(e);
        }

    }

    /**
     * For class.
     * 
     * @param beanClass the bean class
     * @return the introspection cache
     */
    @SuppressWarnings("unchecked")
    public static PropertyDescriptorsCache forClass(Class<?> beanClass) {

        PropertyDescriptorsCache cache;
        Object value = classCache.get(beanClass);
        if (value instanceof Reference) {
            Reference<PropertyDescriptorsCache> ref = (Reference<PropertyDescriptorsCache>) value;
            cache = ref.get();
        } else {
            cache = (PropertyDescriptorsCache) value;
        }

        if (cache == null) {
            cache = new PropertyDescriptorsCache(beanClass);
            classCache.put(beanClass, cache);
        }

        return cache;

    }

    /**
     * Get property descriptors.
     * 
     * @return the property descriptor [ ]
     */
    public PropertyDescriptor[] getPropertyDescriptors() {
        PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()];
        int i = 0;
        for (PropertyDescriptor pd : this.propertyDescriptorCache.values()) {
            pds[i] = pd;
            i++;
        }
        return pds;
    }

    /**
     * Get property descriptor.
     * 
     * @param name the name
     * @return the property descriptor
     */
    public PropertyDescriptor getPropertyDescriptor(String name) {

        PropertyDescriptor pd = this.propertyDescriptorCache.get(name);

        if (pd == null && StringUtils.isNotBlank(name)) {
            // Same lenient fallback checking as in PropertyTypeDescriptor...
            pd = this.propertyDescriptorCache.get(name.substring(0, 1).toLowerCase()
                                                  + name.substring(1));
            if (pd == null) {
                pd = this.propertyDescriptorCache.get(name.substring(0, 1).toUpperCase()
                                                      + name.substring(1));
            }
        }
        return pd;
    }

    /**
     * Get fields.
     * 
     * @return the field [ ]
     */
    public Field[] getFields() {
        Field[] fields = new Field[this.fieldCache.size()];
        int i = 0;
        for (Field f : this.fieldCache.values()) {
            fields[i] = f;
            i++;
        }
        return fields;
    }

}
